package br.com.simtecnologia.access.controll.role;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.caelum.vraptor.ioc.ApplicationScoped;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.simtecnologia.access.controll.annotation.DeniedLogic;
import br.com.simtecnologia.access.controll.annotation.LoginLogic;

@Component
@ApplicationScoped
public class DefaultRoleResolver implements RoleResolver, Serializable {
	
	private static final long serialVersionUID = -4305587157113981441L;
	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRoleResolver.class);
	private static void debug(String regex, Object ... objects) {
		if (LOGGER.isDebugEnabled())
			LOGGER.debug(String.format(regex, objects));
	}

	private void addRoles(final List<String> alloweds,
			final List<String> denieds, Roles logicRoles, Roles resourceLogic) {
		if (logicRoles != null) {
			addRoles(alloweds,denieds,logicRoles);
			if (logicRoles.includeResourceRoles()) {
				addRoles(alloweds,denieds,resourceLogic);
			}
		} else
			addRoles(alloweds,denieds,resourceLogic);
	}
	
	private void addRoles(List<String> alloweds, List<String> denieds,
			Roles roles) {
		if (roles == null)
			return;
		addRoles(alloweds,roles.value());
		addRoles(denieds,roles.denied());
	}

	private void addRoles(List<String> roleList, String[] roleArray) {
		for (String s : roleArray) 
			if (roleArray != null)
				roleList.add(s);
	}
	
	@Override
	public boolean hasAccess(ResourceMethod logic, String... roles) {
		return hasAccess(logic,roles == null ? (Collection<String>)null : Arrays.asList(roles));
	}

	@Override
	public boolean hasAccess(ResourceMethod logic, Collection<String> roles) {
		final List<String> alloweds = new ArrayList<String>();
		final List<String> denieds = new ArrayList<String>();
		
		Roles logicRoles = logic.getMethod().getAnnotation(Roles.class);
		Roles resourceLogic = logic.getResource().getType().getAnnotation(Roles.class);
		
		DeniedLogic deniedLogic = logic.getMethod().getAnnotation(DeniedLogic.class);
		LoginLogic loginLogic = logic.getMethod().getAnnotation(LoginLogic.class);
		
		if (deniedLogic != null || loginLogic != null) {
			LOGGER.warn("the test was make to a LoginLogic or DeniedLogic, acess is always allowed to this annotations");
			return true;
		}
		
		if (logicRoles == null && resourceLogic == null) {
			debug("@Roles annotation not found, access allowed to logic [%s]",logic.getMethod());
			return true;
		} else
			debug("@Roles annotation found to logic [%s]", logic);
		
		if (logicRoles != null && !logicRoles.includeResourceRoles() && logicRoles.value().length == 0) {
			debug("@Roles at @Resource was skiped by \"includeResourceRoles\" property to logic [%s]",logic.getMethod());
			return true;
		}
		
		if (roles == null || roles.isEmpty()) {
			debug("the test was make with out role, access denied to logic [%s]",logic.getMethod());
			return false;
		}
		
		addRoles(alloweds, denieds, logicRoles, resourceLogic);
		
		if (alloweds.isEmpty())
			LOGGER.warn("@Roles not allowed access to any role in logic [%s] check @Roles.value()",logic.getMethod());
		
		
		denieds.retainAll(roles);
		alloweds.retainAll(roles);
		
		return denieds.isEmpty() && !alloweds.isEmpty();
	}
}
